Skip to content

ext_authz: added ability to detect partial request body data#6583

Merged
mattklein123 merged 20 commits intoenvoyproxy:masterfrom
brectanus-sigsci:buffered_authz_partial_flag
Apr 24, 2019
Merged

ext_authz: added ability to detect partial request body data#6583
mattklein123 merged 20 commits intoenvoyproxy:masterfrom
brectanus-sigsci:buffered_authz_partial_flag

Conversation

@brectanus-sigsci
Copy link
Contributor

@brectanus-sigsci brectanus-sigsci commented Apr 15, 2019

This adds a x-envoy-auth-partial-body: false|true metadata header to the CheckRequest call (see #6487). This header is set to true when partial body data is sent in the call when the max_request_bytes setting has been exceeded and the allow_partial_data option is set.

Fixes #6487

Risk Level: low
Testing: unit
Docs Changes: yes
Release Notes: yes

Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
@htuch htuch requested a review from gsagula April 15, 2019 22:58
Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks! @gsagula do you want to give this a pass as well?

@brectanus-sigsci
Copy link
Contributor Author

brectanus-sigsci commented Apr 16, 2019

As I continue testing with this, I am doubting that my simple solution to the issue in #6487 will be sufficient now that 1.10 is out the door and this PR would go in 1.11. @htuch @gsagula - could use advice here.

Here is where I am having issues...

I'm building my gRPC service (in go) to be 1.11 compatible with this field and so I can call (in go) func GetPartialBody() bool no matter what version of envoy the client is using. One issue I am seeing as I am testing more with this feature is that I cannot rely on the partial_body bool field when envoy 1.10 is a client as the field will always be false (field not sent - defaults false). This seems to pose a problem with proto3 as there is no way to detect a non-existent scalar field like proto2. Seems in proto3 there are two ways around this (none of which is overly appealing):

  • Use an enum allowing for a tri-state value where the first (zero; default value) is NOTSET and then a FALSE and TRUE value
  • Use oneof (which allows testing for partial_body_available() == nil indicating envoy 1.10 clients)
oneof partial_body_available {
  bool partial_body = 12;
}

This is generally an issue with the new body field as well. I can call func GetBody() string in go and it will return the empty string for envoy <= 1.10 as a client and there is no way to detect if this is just "no body" or if it is an envoy too old for this feature (and thus should use another workaround I have).

Im now realizing I should have been testing this more as #5824 was progressing. It may have been better to do something similar to the tap filter where the body is not a scalar type and thus not subject to this problem (i.e., it can be nil). Perhaps better is to swap out body for a HttpBody type:

message AttributeContext {
  ...

  message HttpRequest {
    ...

    string body = 11 [deprecated = true];

    // Replace `body` with a non-scalar type to detect availability of the field
    message HttpBody {
      string data = 1; // Possibly: oneof data { string as_string = 1; }
      bool partial = 2;
    }
    HttpBody http_body = 12;
}

This would allow (in go) GetHttpBody() to return nil in the case of it not being set and allow for detection of the body not being available vs an empty body. Thoughts?

// :ref:`allow_partial_message
// <envoy_api_field_config.filter.http.ext_authz.v2.BufferSettings.allow_partial_message>`
// is true.
bool partial_body = 12;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if we should make it available for the HTTP client too. If not, can we add a comment somewhere in the docs that this is gRPC only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, I totally missed this. This complicates things as it seems this extra field does not translate to a plain HTTP request. I suppose it could be added as a header (x-envoy-partial-body: 0|1)?

@gsagula
Copy link
Member

gsagula commented Apr 16, 2019

As I continue testing with this, I am doubting that my simple solution to the issue in #6487 will be sufficient now that 1.10 is out the door and this PR would go in 1.11. @htuch @gsagula - could use advice here.

Here is where I am having issues...

I'm building my gRPC service (in go) to be 1.11 compatible with this field and so I can call (in go) func GetPartialBody() bool no matter what version of envoy the client is using. One issue I am seeing as I am testing more with this feature is that I cannot rely on the partial_body bool field when envoy 1.10 is a client as the field will always be false (field not sent - defaults false). This seems to pose a problem with proto3 as there is no way to detect a non-existent scalar field like proto2. Seems in proto3 there are two ways around this (none of which is overly appealing):

  • Use an enum allowing for a tri-state value where the first (zero; default value) is NOTSET and then a FALSE and TRUE value
  • Use oneof (which allows testing for partial_body_available() == nil indicating envoy 1.10 clients)
oneof partial_body_available {
  bool partial_body = 12;
}

This is generally an issue with the new body field as well. I can call func GetBody() string in go and it will return the empty string for envoy <= 1.10 as a client and there is no way to detect if this is just "no body" or if it is an envoy too old for this feature (and thus should use another workaround I have).

Im now realizing I should have been testing this more as #5824 was progressing. It may have been better to do something similar to the tap filter where the body is not a scalar type and thus not subject to this problem (i.e., it can be nil). Perhaps better is to swap out body for a HttpBody type:

message AttributeContext {
  ...

  message HttpRequest {
    ...

    string body = 11 [deprecated = true];

    // Replace `body` with a non-scalar type to detect availability of the field
    message HttpBody {
      string data = 1; // Possibly: oneof data { string as_string = 1; }
      bool partial = 2;
    }
    HttpBody http_body = 12;
}

This would allow (in go) GetHttpBody() to return nil in the case of it not being set and allow for detection of the body not being available vs an empty body. Thoughts?

@brectanus-sigsci We could add it as header metadata, something like x-envoy-auth-partial: 100. Where value indicates the size of the partial message. If this header is added in check request, all clients (HTTP and gRPC) will have the same feature. WDYT?

@brectanus-sigsci
Copy link
Contributor Author

@brectanus-sigsci We could add it as header metadata, something like x-envoy-auth-partial: 100. Where value indicates the size of the partial message. If this header is added in check request, all clients (HTTP and gRPC) will have the same feature. WDYT?

@gsagula Yes. Just came to that conclusion responding to your other comment. I am not sure a length adds anything here as you can get the length from body. Maybe just x-envoy-auth-partial-body: 0|1? And then I can detect non-existance via missing header metadata.

@gsagula
Copy link
Member

gsagula commented Apr 16, 2019

Sure. I thought about passing the size because it's useful info I guess, but either works. Up to you :)

@brectanus-sigsci
Copy link
Contributor Author

Sure. I thought about passing the size because it's useful info I guess, but either works. Up to you :)

Would the size in the metadata ever be different than the string length of body in gRPC or the content-length header in the HTTP case? Otherwise it seems like redundant info. Originally I had debated this instead of the bool, but could not come up with a scenario where it made sense. It seems the full un-truncated content length might make sense to see how much you are missing, but I don't think that would always be available, so I settled on just bool. I'll stick with bool unless someone can think of a scenario where the length is not redundant. Will update the PR shortly. Thanks!

@gsagula
Copy link
Member

gsagula commented Apr 16, 2019

Sure, thanks for tackling it :)

Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
…ial data

Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
… message

Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
@brectanus-sigsci brectanus-sigsci changed the title ext_authz: added partial_body bool field ext_authz: added ability to detect partial request body data Apr 17, 2019
@brectanus-sigsci
Copy link
Contributor Author

@gsagula I updated to using a metadata header as we discussed to get around some 1.10 compatibility issues I found after more testing.

@htuch The concept changed, so you may want to re-review :)

Copy link
Member

@gsagula gsagula left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brectanus-sigsci Overall looks pretty good. One small comment.

buffer->copyOut(0, length, &data[0]);
httpreq.set_body(std::move(data));

// Add in a header to detect when a partial body is used.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may also want to check that this header does not exist when the request is not a partial message. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly sure what you mean here. Only send the header if the body was actually truncated? Or only send the header if the allow_partial_message option is set...

The metadata header is currently only there if max_request_bytes is set. In order to detect if this feature is available, I needed to send it with a bool (0|1) value (vs only sending the header if the body is partial). This way, in the gRPC service, you can use the logic "if the header is there, then it is coming from an envoy with this feature (v1.11+) and the value indicates partial or not. In this case the work is done for you, otherwise this is an older envoy and you need to detect this on your own using C-L header or other means."

Now, if you mean only write the header if the config option allow_partial_message: true, then yeah, that could be done, but I did not want to add another parameter to CheckRequestUtils::setHttpRequest to expose a check for this. I did not think it was worth it as it meant it was harder to determine from the gRPC service if the new partial flag feature was available. That is, if the header is not sent when allow_partial_message: false, then the gRPC service cannot tell if it is envoy v1.10 or the partial feature is disabled in v1.11.

If you think that the metadata header may get in people's way when allow_partial_message: false, then I can expose the config option as a parameter to CheckRequestUtils::setHttpRequest. Unless maybe you see another way to get access to the config option without it as a parameter?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the client request contains x-envoy-auth-partial-body: 1 header? Not sure if we should trust the client.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I'll skip that header during the copy.

Copy link
Member

@gsagula gsagula left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Just one small comment about preventing x-envoy-auth-partial-body to be added outside the filter.

Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
@brectanus-sigsci
Copy link
Contributor Author

Awesome! Just one small comment about preventing x-envoy-auth-partial-body to be added outside the filter.

OK. Fix for this pushed with updated tests.

Copy link
Member

@gsagula gsagula left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one last comment. Thank you!

* http: mitigated a race condition with the :ref:`delayed_close_timeout<envoy_api_field_config.filter.network.http_connection_manager.v2.HttpConnectionManager.delayed_close_timeout>` where it could trigger while actively flushing a pending write buffer for a downstream connection.
* redis: add support for zpopmax and zpopmin commands.
* upstream: added :ref:`upstream_cx_pool_overflow <config_cluster_manager_cluster_stats>` for the connection pool circuit breaker.
* ext_authz: added a `x-envoy-auth-partial-body` metadata header set to `0|1` indicating if there is a partial body sent in the authorization request message.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use alphabetical order here, please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, and also merged with master to fix the conflict

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol, and that merge destroyed the alpha order I just fixed, so fixed it again. Sigh.

@gsagula Looks like that invalidated your review, too. Sorry.

Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
gsagula
gsagula previously approved these changes Apr 18, 2019
Copy link
Member

@gsagula gsagula left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGMT, thank you!

Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
@repokitteh-read-only
Copy link

🔨 rebuilding ci/circleci: release (failed build)

🐱

Caused by: a #6583 (comment) was created by @brectanus-sigsci.

see: more, trace.

gsagula
gsagula previously approved these changes Apr 18, 2019
Copy link
Member

@gsagula gsagula left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattklein123 Would like to give it a final blessing? Thanks!

@mattklein123 mattklein123 self-assigned this Apr 19, 2019
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I have a design question/comment. Thank you!

/wait-any

bool use_alpha = 4 [deprecated = true];

// Enables filter to buffer the client request body and send it within the authorization request.
// A ``x-envoy-auth-partial-body: 0|1`` metadata header will be added to the authorization
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I would do "true" and "false" here vs. "0" and "1" to clearly indicate boolean, but I don't feel strongly about it. WDYT? A more general question would be why use a header at all vs. adding a boolean field to the request proto?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested using headers here for a couple of reasons. Please see #6583 (comment) for details.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattklein123 true|false vs 0|1 - I have no preference. The bool values are much clearer, so I can change that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have all already discussed headers vs. field and determined header is better that's fine with me, but yeah let's change to true/false if you don't mind. Thank you!

/wait

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattklein123 changed. Thanks.

@htuch htuch removed their assignment Apr 19, 2019
Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
@brectanus-sigsci
Copy link
Contributor Author

/retest

@repokitteh-read-only
Copy link

🔨 rebuilding ci/circleci: coverage (failed build)
🔨 rebuilding ci/circleci: docs (failed build)

🐱

Caused by: a #6583 (comment) was created by @brectanus-sigsci.

see: more, trace.

Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
mattklein123
mattklein123 previously approved these changes Apr 23, 2019
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@mattklein123
Copy link
Member

@brectanus-sigsci sorry needs a master merge.

/wait

Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
Signed-off-by: Brian Rectanus <brectanus@signalsciences.com>
@brectanus-sigsci
Copy link
Contributor Author

@mattklein123 conflict resolved. Hopefully checks pass the first time :)

Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@mattklein123 mattklein123 merged commit 77d267e into envoyproxy:master Apr 24, 2019
mpuncel added a commit to mpuncel/envoy that referenced this pull request Apr 24, 2019
* master:
  docs: add extension policy (envoyproxy#6678)
  ext_authz: added ability to detect partial request body data (envoyproxy#6583)
  version_history.rst: jwt_authn change missed 1.10.0 (envoyproxy#6684)
  docs: fix link in pull request template (envoyproxy#6679)
  Explicitly convert absl::string_view to std::string. (envoyproxy#6687)
  docs: improving watermark docs/comments (envoyproxy#6683)
  http filter: add CSRF filter (envoyproxy#6470)
  event: reintroduce dispatcher stats (envoyproxy#6659)
  security: postmortem for CVE-2019-990[01] (envoyproxy#6597)
  Improve build rules for (test only) library quic_port_utils. (envoyproxy#6672)
  spell check: skip unsupported extensions when called with a file (envoyproxy#6648)
  Changed TestHooks to ListenerHooks (envoyproxy#6642)
  proto: move extension-specific linking validation into extensions (envoyproxy#6657)
  stats: add/test heterogenous set of StatNameStorage objects. (envoyproxy#6504)
  docs: move xds protocol to rst (envoyproxy#6670)
  fix version history order (envoyproxy#6671)

Signed-off-by: Michael Puncel <mpuncel@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot detect partial body content sent in ext_authz call with allow_partial_message

4 participants